/*******************************************************************
This file is part of iDialer.

iDialer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

iDialer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with iDialer.  If not, see <http://www.gnu.org/licenses/>.
*******************************************************************/

#include "stdafx.h"
#include "iDialer.h"

#include "FileUtils.h"
#include "GraphicsUtils.h"
#include "PhoneUtils.h"
#include "RegistryUtils.h"
#include "SoundUtils.h"
#include "Titlebar.h"
#include "Version.h"

#include <phone.h>
#include <wingdi.h>
#include <pimstore.h>
#include <astdtapi.h>
#include <strsafe.h>
#include <regext.h>
#include <snapi.h>

#include <htmlctrl.h>

#pragma comment(lib, "phone.lib") 
#pragma comment(lib, "cellcore.lib")
#pragma comment(lib, "htmlview.lib")

#include <regext.h>

#include "HTTP.h"

//-----------------------------------------------------------------------------
// Global data
//
HINSTANCE   hInst;                     // Program instance handle
HWND hwndMain = 0;
HWND hwndHTML = 0;
bool        bDialImmediately = false;

bool        biContactExists = false;
int         nHighlightedTab = 3;
bool        bKeysDialed = false;

HDC			hdcSkin = NULL;
HBITMAP		hbmSkin = NULL;
HDC         hdcMem = NULL;
HBITMAP     hbmMem = NULL;
HDC         hdcContent = NULL;
HBITMAP     hbmContent = NULL;
HDC         hdcTmp = NULL;
HBITMAP     hbmTmp = NULL;

HFONT       hfNumberFont = NULL;
HFONT       hfDigitFont = NULL;
HFONT       hfTitlebarFont = NULL;
HFONT       hfCaptionFont = NULL;

// UI Element Sizes. These can't be static because
// of different DPI devices
int         vga = 1; // 1 = qvga, 2 = vga

RECT        rScreen = {0};
RECT        rTitlebar = {0};
RECT        rMenubar = {0};
RECT        rContent = {0};
RECT        rDigits = {0};
RECT        rect1 = {0};
RECT        rect2 = {0};
RECT        rect3 = {0};
RECT        rect4 = {0};
RECT        rect5 = {0};
RECT        rect6 = {0};
RECT        rect7 = {0};
RECT        rect8 = {0};
RECT        rect9 = {0};
RECT        rect0 = {0};
RECT        rects = {0};
RECT        rectp = {0};
RECT        rAddButton = {0};
RECT        rCallButton = {0};
RECT        rBackButton = {0};
RECT        rClickRegion = {0};
POINT       ptMouseDown = { -1, -1 };
bool        bMouseDown = false;
bool        bKeyDown = false;
TCHAR       tcButtonPressed = 0;
TCHAR       dialNumber[MAX_DIALSTRING] = {0};
TCHAR       dialString[MAX_DIALSTRING] = {0};
TCHAR       dialName[TAPIMAXCALLEDPARTYSIZE] = {0};

// Program Settings
TCHAR       skinFilename[MAX_PATH] = {0};
TCHAR       szCountryCode[MAX_COUNTRY_CODE] = {0};
TCHAR       szIntlPrefix[MAX_COUNTRY_CODE] = {0};
TCHAR       szMyNumber[MAX_DIALSTRING] = {0};
bool        bExitOnMinimize = false;
bool		bShowFullScreen = true;
UINT		uVolume = 0x2020;

// phone services (Phone, CallingCard, GoogleVoice, JaJah, Command)
int         nService = 0;
int         cServices = 0;
SERVICE     services[5];

// speed dial
SPEEDDIALINFO speedDialInfo[10] = {0};

// keep track of if the settings get updated while the program is running
HREGNOTIFY  hNotify;

TCHAR       dialPlan[MAX_DIALPLAN];

// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_ACTIVATE, DoActivate,
    WM_COMMAND, DoCommand,
    WM_CONTEXTMENU, DoContextMenu,
    WM_COPYDATA, DoCopyData,
    WM_DESTROY, DoDestroyMain,
    WM_KEYDOWN, DoKeyDown,
    WM_KEYUP, DoKeyUp,
    WM_LBUTTONDOWN, DoLButtonDown,
    WM_LBUTTONUP, DoLButtonUp,
    WM_MOUSEMOVE, DoMouseMove,
	WM_NOTIFY, DoNotify,
    WM_PAINT, DoPaintMain,
    WM_REGISTRY, DoRegistryCallback,
    WM_SIZE, DoSize,
    WM_TIMER, DoTimer,
    WM_TITLEBAR, DoTitlebarCallback,
};

//=============================================================================
//
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;

    // Initialize this instance.
    hwndMain = InitInstance(hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0)
        return 0x10;

    // Due to a command-line option
    if (bDialImmediately)
        PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL, nService);

    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}

//-----------------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
    WNDCLASS wc;
    HWND hWnd;
    COPYDATASTRUCT cdData;

    // Save program instance handle in global variable.
    hInst = hInstance;

#if defined(WIN32_PLATFORM_PSPC) || defined(WIN32_PLATFORM_WFSP)
    // If Windows Mobile, allow only one instance of the application.
    hWnd = FindWindow (SZ_APP_NAME, NULL);
    if (hWnd) {
        SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01));
        cdData.dwData = NULL;
        cdData.cbData = sizeof(wchar_t) * _tcslen(lpCmdLine);
        cdData.lpData = lpCmdLine;
        SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM) (LPVOID) &cdData);
        return 0;
    }
#endif

    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = 0;                        // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL;                          // Application icon
    wc.hCursor = NULL;                        // Default cursor
    wc.hbrBackground = NULL;                  // Don't erase the background
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = SZ_APP_NAME;           // Window class name
    if (RegisterClass (&wc) == 0) return 0;

    // Create main window.
    hWnd = CreateWindowEx(WS_EX_NODRAG,       // Ex Style
                         SZ_APP_NAME,         // Window class
                         SZ_APP_NAME,         // Window title
                         WS_SYSMENU,          // Style flags
                                              // don't use WS_VISIBLE.
                                              // Why not? It causes an "error report"
                                              // Why? I don't know.
                         CW_USEDEFAULT,       // x position
                         CW_USEDEFAULT,       // y position
                         CW_USEDEFAULT,       // Initial width
                         CW_USEDEFAULT,       // Initial height
                         NULL,                // Parent
                         NULL,                // Menu, must be null
                         hInstance,           // Application instance
                         NULL);               // Pointer to create
                                              // parameters

    // Return fail code if window not created.
    if (!IsWindow (hWnd)) return 0;

    // Additional initialization
    // **************************
    LoadSettings();

    // maybe we don't need to go any further than this...
    if (0 == ParseCommandLine(lpCmdLine))
        return 0;

    biContactExists = iContactExists();
	LoadSpeedDialInfo(speedDialInfo);

    // If the "service" setting is modified while I'm running
    // then LoadSettings() again
    HRESULT hr = RegistryNotifyWindow(
        HKEY_CURRENT_USER, 
        SZ_IDIALER_REG_KEY, 
        SERVICE_NUM, 
        hWnd, WM_REGISTRY, CMD_RELOAD_SETTINGS,
        NULL, &hNotify);

    FillDialString();

    // Perform DPI adjustments
    HDC hdc = GetDC(hWnd);
	int res = min(::GetDeviceCaps(hdc, HORZRES), ::GetDeviceCaps(hdc, VERTRES));
    vga = MulDiv(vga, res, DEFAULT_SCREEN_WIDTH);

	// Create fonts
    hfNumberFont = BuildFont(NUMBER_FONT_SIZE * vga, false, false);
    hfDigitFont = BuildFont(DIGIT_FONT_SIZE * vga, true, false);
    hfTitlebarFont = BuildFont(TITLEBAR_FONT_SIZE * vga, false, false);
    hfCaptionFont = BuildFont(CAPTION_FONT_SIZE * vga, false, false);

    // Initialize titlebar callbacks
	if (bShowFullScreen)
	    InitTitlebar(hWnd);

    // Initialize the regions, skin, and bitmaps
    InitSurface(hWnd);

    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}

//-----------------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {
    return nDefRC;
}

//=============================================================================
// Message handling procedures for MainWindow
//

//-----------------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam, 
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message. If in list, call procedure.
    //
    for (i = 0; i < ARRAYSIZE(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoPaintMain - Process WM_PAINT message for window.
//
LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    PAINTSTRUCT ps;
    RECT rect;
    HDC hdc;
    HFONT hfOld;
    
    int rScreenWidth = rScreen.right - rScreen.left;
    int rScreenHeight = rScreen.bottom - rScreen.top;   

    hdc = BeginPaint (hWnd, &ps); 

    // rect is the region that needs to be painted
    rect = ps.rcPaint;

    // Draw the main content
    BitBlt(hdcMem, rContent.left, rContent.top, 
        rScreenWidth, rContent.bottom - rContent.top,
        hdcContent, rContent.left, rContent.top, SRCCOPY);

    int dialStringLen = _tcslen(dialString);

    // Draw "About"
    if (dialStringLen == 0) {
        hfOld = (HFONT)SelectObject(hdcMem, hfCaptionFont);
        SetTextAlign(hdcMem, TA_LEFT);
	    SetTextColor(hdcMem, GetSkinRGB(SKIN_COLOR_TEXT_DIM));
        ExtTextOut(hdcMem, rDigits.left + 2, 
            rDigits.bottom - CAPTION_FONT_SIZE * vga - 2,
            NULL, NULL, SZ_ABOUT, _tcslen(SZ_ABOUT), 0);
        SelectObject(hdcMem, hfOld);
    }

    // Draw the dialed number
    // (or the last dialed number in gray)
    else {
        SetTextColor(hdcMem, GetSkinRGB(
			bKeysDialed ? SKIN_COLOR_TEXT : SKIN_COLOR_TEXT_DIM));
        SetTextAlign(hdcMem, TA_CENTER);
	    SetBkMode(hdcMem, TRANSPARENT);

        hfOld = (HFONT)SelectObject(hdcMem, hfNumberFont);

        // Make sure the number will fit on the screen,
        // and decrease the font size until it will fit
        RECT rc = {0,0,0,0};
        int fontsize = NUMBER_FONT_SIZE * vga;
        DrawText(hdcMem, dialString, -1, &rc, DT_CALCRECT);
        while (rc.right > rContent.right && fontsize > 4) {
            fontsize -= 2;
            rc.right = rc.bottom = 0;
            SelectObject(hdcMem, BuildFont(fontsize, false, false));
            DrawText(hdcMem, dialString, -1, &rc, DT_CALCRECT);
        }

        ExtTextOut(hdcMem, (rDigits.right + rDigits.left) / 2, 
            (rDigits.bottom + rDigits.top - fontsize) / 2,
            NULL, NULL, dialString, dialStringLen, 0);
        SelectObject(hdcMem, hfOld);
    }

    // draw the menu bar
    if (biContactExists && rect.bottom > rMenubar.top) {
        int rMenubarWidth = rMenubar.right - rMenubar.left;
		
		// this is only necessary if in landscape mode
		if (rMenubarWidth > DEFAULT_SCREEN_WIDTH * vga) {
			// Copy the first (1 * vga) columns of the 
			// menu bar from skin to memory
			BitBlt(hdcMem, 0, rMenubar.top, vga, SKIN_MENU_BAR_Y_OFFSET * vga,
				hdcSkin, 0, SKIN_MENU_BAR_HEIGHT * vga, SRCCOPY);

			// This will copy the first (1 * vga) columns of 
			// the menu bar fully across the screen
			for (int x = vga; x < rMenubarWidth; x *= 2) {
				int w = 2 * x > rMenubarWidth ? rMenubarWidth - x : x;
				BitBlt(hdcMem, x, rMenubar.top, w, SKIN_MENU_BAR_HEIGHT * vga,
					hdcMem, 0, rMenubar.top, SRCCOPY);
			}
		}

        // draw buttons
        for (int i = 0; i < 5; i++) {
            int xdest = rMenubar.left 
                + rMenubarWidth / 10 * (2 * i + 1) - MENU_BAR_ICON_WIDTH * vga / 2;
            int ydest = rMenubar.top;
            int xsrc = i * SKIN_MENU_BAR_ICON_WIDTH * vga;
            int ysrc = i == nHighlightedTab 
                ? SKIN_MENU_BAR_SEL_Y_OFFSET * vga 
                : SKIN_MENU_BAR_Y_OFFSET * vga;
            BitBlt(hdcMem, xdest, ydest, MENU_BAR_ICON_WIDTH * vga, SKIN_MENU_BAR_HEIGHT * vga, 
                hdcSkin, xsrc, ysrc, SRCCOPY);
        }
    }

    // Titlebar
    if (rect.top < rTitlebar.bottom) {
        DrawTitlebarOn(hdcMem, rTitlebar, hdcSkin, hfTitlebarFont, SZ_APP_NAME);
    }

	// Name of service, under "call" button
	if (cServices > 1) {
        SelectObject(hdcMem, hfCaptionFont);
        SetTextAlign(hdcMem, TA_LEFT);
		SetTextColor(hdcMem, GetSkinRGB(SKIN_COLOR_TEXT));

		DrawText(hdcMem, 
			nService == -1 ? TEXT_ASK : services[nService].title,
			-1, &rCallButton, DT_CENTER | DT_BOTTOM | DT_END_ELLIPSIS);
	}

    // Highlight the region being clicked right now
    if ( bKeyDown || (bMouseDown && PtInRect(&rContent, ptMouseDown)) ) {
        FillRect(hdcTmp, &rClickRegion, 
			CreateSolidBrush(GetSkinRGB(SKIN_COLOR_HIGHLIGHT)));

        BltAlpha(hdcMem, rClickRegion.left, rClickRegion.top, 
            rClickRegion.right - rClickRegion.left,
            rClickRegion.bottom - rClickRegion.top,
            hdcTmp, rClickRegion.left, rClickRegion.top,
            rClickRegion.right - rClickRegion.left,
            rClickRegion.bottom - rClickRegion.top,
            128);
    }

    // Transfer everything to the actual screen
    BitBlt(hdc, rect.left, rect.top, rect.right - rect.left,
        rect.bottom - rect.top, hdcMem, rect.left, rect.top, SRCCOPY);

    SelectObject(hdcMem, hfOld);
    EndPaint (hWnd, &ps);

    return 0;
}

//-----------------------------------------------------------------------------
// DoActivate - Process WM_ACTIVATE message for window
//
LRESULT DoActivate (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    DWORD dwState;
    RECT rc;

    if (wParam == WA_CLICKACTIVE || wParam == WA_ACTIVE) {
        // To switch to full screen mode, first hide all of the shell parts.
        // Do not enable SHFS_HIDESTARTICON; 
        // it breaks the volume control on Diamond
		
		if (bShowFullScreen) {
			dwState = SHFS_HIDETASKBAR | SHFS_HIDESIPBUTTON;

			// Resize the main window to the size of the screen.
			SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSCREEN), 
				GetSystemMetrics(SM_CYSCREEN));
		}
		else {
			dwState = SHFS_SHOWTASKBAR | SHFS_HIDESIPBUTTON;

			// Resize the main window to the size of the work area.
			SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, FALSE);
		}

		SHFullScreen(hWnd, dwState);

		MoveWindow(hWnd, rc.left, rc.top, rc.right-rc.left, 
			rc.bottom-rc.top, TRUE);

		// Hide SIP button displayed by iContactAE
		SipShowIM(SIPF_OFF);
		ShowWindow(FindWindow(L"MS_SIPBUTTON", NULL), SW_HIDE);

        nHighlightedTab = 3;
        bMouseDown = false;
        bKeyDown = false;

        ShowWindow(hWnd, SW_SHOWNORMAL);
    }


    // The window is being deactivated and another program 
    // is being run... so minimize
    // lParam == NULL when the window being activated is not this app.
    else if (lParam == NULL) {
        if (bExitOnMinimize) {
            DestroyWindow(hWnd);
        }
    }

    // The window is being deactivated 
    // (maybe for "manage contact", or "edit contact")... 
    // restore it to non-fullscreen
    else {
        // To switch to normal mode, first show all of the shell parts.
        dwState = (SHFS_SHOWTASKBAR 
            | SHFS_SHOWSTARTICON 
            | SHFS_SHOWSIPBUTTON);

        SHFullScreen(hWnd, dwState);

        // Next resize the main window to the size of the work area.
        SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, FALSE);

        MoveWindow(hWnd, rc.left, rc.top, rc.right-rc.left,
            rc.bottom-rc.top, TRUE);
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoCommand - Process WM_COMMAND message for window
//
LRESULT DoCommand (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
    int len, index;
    TCHAR szBuffer[MAX_PATH] = {0};
    PROCESS_INFORMATION pi;
    HRESULT hr = E_FAIL;

    switch (wParam) {
        case CMD_GOTO_FAVORITES:
            if (!GetIContactFilename(szBuffer))
                break;
            nHighlightedTab = 0;
            CreateProcess(szBuffer, ICONTACT_FAVORITES,
                NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
            break;

        case CMD_GOTO_RECENTS:
            if (!GetIContactFilename(szBuffer))
                break;
            nHighlightedTab = 1;
            CreateProcess(szBuffer, ICONTACT_RECENTS,
                NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
            break;

        case CMD_GOTO_CONTACTS:
            if (!GetIContactFilename(szBuffer))
                break;
            nHighlightedTab = 2;
            CreateProcess(szBuffer, ICONTACT_CONTACTS,
                NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
            break;

        case CMD_GOTO_SEARCH:
            if (!GetIContactFilename(szBuffer))
                break;
            nHighlightedTab = 4;
            CreateProcess(szBuffer, ICONTACT_SEARCH,
                NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);
            break;

        case CMD_DIAL_DIGIT:

            len = _tcslen(dialNumber);
            if (lParam == VK_BACK) {
                // this erases the last dialed number
                if (!bKeysDialed) {
                    bKeysDialed = true;
                    dialNumber[0] = 0;
					dialName[0] = 0;
                }
                else if (len > 0) {
                    dialNumber[len-1] = 0;
                }
                FillDialString();
            }
            else if (lParam == VK_TSOFT1) {
                AddContact();
            }
            else if (lParam == VK_RETURN) {
                PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, nService);
            }
            else {
                // this erases the last dialed number
                if (!bKeysDialed) {
                    bKeysDialed = true;
                    len = 0;
					dialName[0] = 0;
                }
                if (len < MAX_DIALSTRING - 1) {
                    dialNumber[len] = (TCHAR)lParam;
                    dialNumber[len+1] = 0;
                    FillDialString();
                }
            }
            break;

        case CMD_CLEAR_DIGITS:
            dialNumber[0] = 0;
			dialName[0] = 0;
            FillDialString();
            break;

		case CMD_PLACE_CALL:
            // Store the dialed number for next use
            SaveSetting(SZ_IDIALER_REG_KEY, dialNumber, LAST_DIALED_NUMBER);

			if (0 == _tcslen(dialName)) {
				StringCchCopy(dialName, MAX_DIALSTRING, dialString);
			}

			// implementation of "ask"
			if (lParam == -1) {
				HMENU hPopupMenu = CreatePopupMenu();
				for (int i = 0; i < cServices; i++) {
					StringCchPrintf(szBuffer, MAX_PATH, TEXT("%s %s"), 
						TEXT_CALL_USING, services[i].title);
					AppendMenu(hPopupMenu, MF_STRING, i+1, szBuffer);
				}

				SetForegroundWindow(hWnd);
				int result = TrackPopupMenu(hPopupMenu,
					TPM_CENTERALIGN | TPM_VCENTERALIGN | TPM_RETURNCMD,
					rContent.right / 2, rContent.bottom / 2, 0, hWnd, NULL);

				// result may be "0", or 1 <= result <= cServices
				if (!result)
					break;

				lParam = result - 1;
			}

			if (lParam >= cServices || lParam < 0)
				break;

			hr = Dial(dialNumber, dialName, lParam);
            break;

        case CMD_RELOAD_SETTINGS:
            LoadSettings();
            break;

		case CMD_DIAL_VOICEMAIL:
            StringCchCopy(dialNumber, MAX_DIALSTRING, szMyNumber);
            StringCchCopy(dialName, TAPIMAXCALLEDPARTYSIZE, TEXT_VOICEMAIL);
            PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, nService);
            break;

		case CMD_SPEED_DIAL:
			if (lParam == '1') {
				PostMessage(hWnd, CMD_DIAL_VOICEMAIL, 0, 0);
				break;
			}

			index = lParam - '0';
			if (index < 2 || index > 9)
				break;

			if (0 == _tcslen(speedDialInfo[index].number))
				break;

            bKeysDialed = false;
			StringCchCopy(dialNumber, MAX_DIALSTRING, speedDialInfo[index].number);
			StringCchCopy(dialName, MAX_DIALSTRING, speedDialInfo[index].name);
            PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, nService);
            break;

        case CMD_PLACE_CALL_COMPLETE:
            bKeysDialed = false;
            ShowWindow(hWnd, SW_MINIMIZE);
            break;

        case CMD_CHANGE_SERVICE:
			nService++;
			if (nService >= cServices || nService < -1)
				nService = -1;
            StringCchPrintf(szBuffer, MAX_PATH, TEXT("%d"), nService);
            SaveSetting(SZ_IDIALER_REG_KEY, szBuffer, SERVICE_NUM);
            break;

        case CMD_CHANGE_SERVICE_TO:
			nService = lParam;
			if (nService >= cServices || nService < -1)
				nService = -1;
            StringCchPrintf(szBuffer, MAX_PATH, TEXT("%d"), nService);
            SaveSetting(SZ_IDIALER_REG_KEY, szBuffer, SERVICE_NUM);
            break;

        case CMD_CUT:
			SendMessage(hWnd, WM_COMMAND, CMD_COPY, NULL);
			dialNumber[0] = 0;
			FillDialString();
			dialName[0] = 0;
            break;

        case CMD_COPY:
			if (OpenClipboard(NULL)) {
				HLOCAL clipbuffer;
				EmptyClipboard();
				clipbuffer = LocalAlloc(0, (_tcslen(dialString) + 1) * sizeof TCHAR);
				StringCchCopy((TCHAR *)clipbuffer, MAX_PATH, dialString);
				// Set UNICODE clipboard data
				SetClipboardData(CF_UNICODETEXT, clipbuffer);
				CloseClipboard();
				LocalFree(clipbuffer);
			}
			break;

        case CMD_PASTE:
			if (OpenClipboard(NULL)) {
				HANDLE hData = GetClipboardData(CF_UNICODETEXT);
				StripNonDigits(szBuffer, (TCHAR *)hData, MAX_PATH);
				if (_tcslen(szBuffer) > 0) {
					StringCchCopy(dialNumber, MAX_DIALSTRING, szBuffer);
					FillDialString();
				}
				CloseClipboard();
			}
            break;

        case CMD_GREEN_BUTTON:
            PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, nService);
            break;

        case CMD_RED_BUTTON:
            ShowWindow(hWnd, SW_MINIMIZE);
            break;




    }

    InvalidateRect(hWnd, &rScreen, FALSE);
    UpdateWindow(hWnd);

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoCopyData - Process WM_COPYDATA message for window
//
// We're only using WM_COPYDATA to pass a command line from a newly created
// instance of iDialer to an already running instance.
//
LRESULT DoCopyData (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
    PCOPYDATASTRUCT pcdData;
    pcdData = (PCOPYDATASTRUCT) lParam;

    int result = ParseCommandLine((TCHAR*)&pcdData->lpData);

    // If we want to exit/minimize now, then do it!
    if (result == 0)
        ShowWindow(hWnd, SW_MINIMIZE);

    else if (bDialImmediately)
        PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, nService);

    return 0;
}

//-----------------------------------------------------------------------------
// DoTitlebarCallback - Process WM_REGISTRY message for window
//
LRESULT DoTitlebarCallback (HWND hWnd, UINT wMsg, WPARAM wParam,
                    LPARAM lParam) {
    RefreshTitlebar(lParam);
    InvalidateRect(hWnd, &rTitlebar, false);
    return 0;
}

//-----------------------------------------------------------------------------
// DoRegistryCallback - Process WM_REGISTRY message for window
//
LRESULT DoRegistryCallback (HWND hWnd, UINT wMsg, WPARAM wParam,
                    LPARAM lParam) {

    if (lParam == CMD_RELOAD_SETTINGS) {
        LoadSettings();
    }
    InvalidateRect(hWnd, &rTitlebar, false);
    return 0;
}

//-----------------------------------------------------------------------------
// DoSize - Process WM_SIZE message for window
//
LRESULT DoSize (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    switch (wParam) {
        case SIZE_RESTORED:
        case SIZE_MAXIMIZED:
        case SIZE_MAXSHOW:
	        InitSurface(hWnd);
            return 0;
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoLButtonDown - Process WM_LBUTTONDOWN message for window
//

LRESULT DoLButtonDown (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    ptMouseDown.x = LOWORD(lParam);
    ptMouseDown.y = HIWORD(lParam);

    bMouseDown = true;
    tcButtonPressed = GetDigitFor(ptMouseDown);
    CalculateClickRegion(ptMouseDown);

	if (PtInRect(&rMenubar, ptMouseDown)) {
		nHighlightedTab = ptMouseDown.x * 5 / rMenubar.right;
	}

    PlayDTMF(tcButtonPressed, uVolume);

    SetTimer(hWnd, IDT_TIMER_LONG_PRESS, 100, NULL);

    InvalidateRect(hWnd, &rClickRegion, false);
    
    return 0;
}

//-----------------------------------------------------------------------------
// DoMouseMove - Process WM_MOUSEMOVE message for window
//

LRESULT DoMouseMove (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    POINT pt;
    pt.x = LOWORD(lParam);
    pt.y = HIWORD(lParam);

    if (PtInRect(&rMenubar, ptMouseDown)) {
        nHighlightedTab = PtInRect(&rMenubar, pt) 
			? pt.x * 5 / rMenubar.right
			: 3;
        InvalidateRect(hWnd, &rClickRegion, false);
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoNotify - Process WM_NOTIFY message for window
//

LRESULT DoNotify (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    NMHDR * pnmh = (NMHDR *)lParam;
	NM_HTMLVIEW * pnmHTMLView = (NM_HTMLVIEW *)lParam;

	// TODO: not exactly sure why this is needed:
	TCHAR url[512];
	mbstowcs(url, (LPCSTR)pnmHTMLView->szTarget, 512);

	IDispatch *pDisp;

    switch (pnmh->code) {
        case NM_BEFORENAVIGATE:
#ifndef DEBUG
			ShowWindow(hwndHTML, SW_HIDE);
#endif
            SHFullScreen(hWnd, SHFS_HIDESIPBUTTON);
            SetCursor(LoadCursor(NULL, IDC_WAIT));
            break;

        case NM_DOCUMENTCOMPLETE:
            // The user needs to log in first. Show the window.
			if (_tcsstr(url, TEXT("/accounts/ServiceLogin"))) {
                SetCursor(NULL);
                ShowWindow(hwndHTML, SW_SHOW);
                SHFullScreen(hWnd, SHFS_SHOWSIPBUTTON);
				break;
			}

            // This is the standard GV control panel. Schedule the call!
			else if (
				   _tcsstr(url, TEXT("/voice/m/sms?"))
				|| _tcsstr(url, TEXT("/voice/b/0/m/sms"))
			) {
				// http://blogs.technet.com/suvarnas/archive/2006/01/13/417387.aspx
				SendMessage(hwndHTML, DTM_DOCUMENTDISPATCH,
                    (WPARAM)0, (LPARAM)&pDisp );
				if( NULL == pDisp)
					break;

				// TODO: fix this for "ask"
                TCHAR * pszCallback = nService >= 0 && _tcslen(services[nService].data)
                    ? services[nService].data
                    : szMyNumber;

				GoogleVoiceMakeCall(pszCallback, dialNumber, pDisp);
			}

            // The call has been scheduled, aaand we're done.
            else if (
				   _tcsstr(url, TEXT("/voice/m/sendcall"))
			) {
                SetCursor(NULL);
#ifndef DEBUG
                PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
#endif
            }

			// Why would URL be empty? No connectivity? Timeout?
			else if (_tcslen(url) == 0) {
				MessageBox(hwndMain, TEXT("Cannot connect!"), NULL, 0);
				SetCursor(NULL);
			}

			// Apparently this happens if you have Cookies turned off in PIE
			else if (_tcsstr(url, TEXT("/accounts/CheckCookie"))) {
				MessageBox(hwndMain, TEXT("Error checking cookie. ")
					TEXT("Please make sure you have cookies enabled ")
					TEXT("in Internet Explorer."), NULL, 0);
				SetCursor(NULL);
			}

            // How did we get here?
            else {
                TCHAR buffer[512];
                StringCchPrintf(buffer, 512, 
                    TEXT("Unexpected results from Google Voice! ")
                    TEXT("Please report to support@supware.net:%s"), url);
                MessageBox(hwndMain, buffer, NULL, 0);
                SetCursor(NULL);
            }
            break;
    }

	return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoLButtonUp - Process WM_LBUTTONUP message for window
//

LRESULT DoLButtonUp (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    if (!bMouseDown)
        return DefWindowProc (hWnd, wMsg, wParam, lParam);

    POINT pt;
	pt.x = LOWORD(lParam);
	pt.y = HIWORD(lParam);
    bMouseDown = false;
    KillTimer(hWnd, IDT_TIMER_LONG_PRESS);
    InvalidateRect(hWnd, &rClickRegion, FALSE);

    StopDTMF();

	nHighlightedTab = PtInRect(&rMenubar, pt) 
		? pt.x * 5 / rMenubar.right 
		: 3;

    // They clicked in the titlebar
	if (PtInRect(&rTitlebar, ptMouseDown) && PtInRect(&rTitlebar, pt)) {
        ShowWindow(hWnd, SW_MINIMIZE);
	}

    // They clicked the bottom menubar
    else if (PtInRect(&rMenubar, ptMouseDown) && PtInRect(&rMenubar, pt)) {
        switch (nHighlightedTab) {
            case 0:
                PostMessage(hWnd, WM_COMMAND, CMD_GOTO_FAVORITES, NULL);
                break;
            case 1:
                PostMessage(hWnd, WM_COMMAND, CMD_GOTO_RECENTS, NULL);
                break;
            case 2:
                PostMessage(hWnd, WM_COMMAND, CMD_GOTO_CONTACTS, NULL);
                break;
            case 3:
				PostMessage(hWnd, WM_COMMAND, CMD_CHANGE_SERVICE, NULL);
                break;
            case 4:
                PostMessage(hWnd, WM_COMMAND, CMD_GOTO_SEARCH, NULL);
                break;
        }
    }

    else if (tcButtonPressed == GetDigitFor(pt) && 0 != tcButtonPressed) {
        PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT, (LPARAM)tcButtonPressed);
    }

    tcButtonPressed = 0;

    return 0;
}

//-----------------------------------------------------------------------------
// DoTimer - Process WM_TIMER message for window
//
LRESULT DoTimer (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    SHRGINFO shrg = {0};
    KillTimer(hWnd, IDT_TIMER_LONG_PRESS);
    
    if (tcButtonPressed == VK_BACK
		|| tcButtonPressed == VK_RETURN
        || tcButtonPressed == '0' 
        || tcButtonPressed == '1'
		|| tcButtonPressed > '1' && tcButtonPressed <= '9' 
			&& _tcslen(speedDialInfo[tcButtonPressed - '0'].number)
		|| PtInRect(&rDigits, ptMouseDown)) {

        shrg.cbSize = sizeof(SHRGINFO);
        shrg.hwndClient = hWnd;
        shrg.ptDown.x = ptMouseDown.x;
        shrg.ptDown.y = ptMouseDown.y;
        shrg.dwFlags = SHRG_RETURNCMD;

        SHRecognizeGesture(&shrg);
    }

    return 0;
}

//-----------------------------------------------------------------------------
// DoContextMenu - Process WM_CONTEXTMENU message for window
//
LRESULT DoContextMenu (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
	HMENU hPopupMenu = 0;
	TCHAR szMenuItem[64] = {0};
	BOOL result = FALSE;

	if (PtInRect(&rDigits, ptMouseDown)) {
		UINT cutcopyflag = _tcslen(dialString) > 0 ? MF_ENABLED : MF_GRAYED;
		UINT pasteflag = IsClipboardFormatAvailable(CF_UNICODETEXT) ? MF_ENABLED : MF_GRAYED;

		hPopupMenu = CreatePopupMenu();
		AppendMenu(hPopupMenu, MF_STRING | cutcopyflag, CMD_CUT, TEXT("Cut"));
		AppendMenu(hPopupMenu, MF_STRING | cutcopyflag, CMD_COPY, TEXT("Copy"));
		AppendMenu(hPopupMenu, MF_STRING | pasteflag, CMD_PASTE, TEXT("Paste"));

		result = TrackPopupMenu(hPopupMenu,
			TPM_CENTERALIGN | TPM_VCENTERALIGN | TPM_RETURNCMD,
			ptMouseDown.x, ptMouseDown.y, 0, hWnd, NULL);
		
		if (result) {
            PostMessage(hWnd, WM_COMMAND, result, 0);
		}
		bMouseDown = false;
		return 0;
	}

	else if (tcButtonPressed == VK_RETURN) {
		hPopupMenu = CreatePopupMenu();
		for (int i = 0; i < cServices; i++) {
			StringCchPrintf(szMenuItem, MAX_PATH, TEXT("%s %s"), 
                TEXT_CALL_USING, services[i].title);
			AppendMenu(hPopupMenu, MF_STRING, i+1, szMenuItem);
		}

		SetForegroundWindow(hWnd);
		result = TrackPopupMenu(hPopupMenu,
			TPM_CENTERALIGN | TPM_VCENTERALIGN | TPM_RETURNCMD,
			ptMouseDown.x, ptMouseDown.y, 0, hWnd, NULL);

		// result may be "0", or 1 <= result <= cServices
		if (result) {
            PostMessage(hWnd, WM_COMMAND, CMD_PLACE_CALL, result - 1);
		}
		bMouseDown = false;
        tcButtonPressed = 0;
		return 0;
	}

	else if (tcButtonPressed == VK_BACK) {
        PostMessage(hWnd, WM_COMMAND, CMD_CLEAR_DIGITS, NULL);
		bMouseDown = false;
        tcButtonPressed = 0;
        return 0;
	}

	else if (tcButtonPressed == '0') {
        StopDTMF();
        PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT, (LPARAM)'+');
		bMouseDown = false;
        tcButtonPressed = 0;
        return 0;
	}

	else if (tcButtonPressed == '1') {
        StopDTMF();
        PostMessage(hWnd, WM_COMMAND, CMD_DIAL_VOICEMAIL, NULL);
		bMouseDown = false;
        tcButtonPressed = 0;
        return 0;
	}

	else if (tcButtonPressed >= '2' && tcButtonPressed <= '9') {
		StopDTMF();
		PostMessage(hWnd, WM_COMMAND, CMD_SPEED_DIAL, tcButtonPressed);
		bMouseDown = false;
		tcButtonPressed = 0;
		return 0;
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoKeyDown - Process WM_KEYDOWN message for window
//
LRESULT DoKeyDown (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

	switch (wParam) {
        case VK_LEFT:
            PostMessage(hWnd, WM_COMMAND, CMD_GOTO_CONTACTS, NULL);
            return 0;

        case VK_RIGHT:
            PostMessage(hWnd, WM_COMMAND, CMD_GOTO_SEARCH, NULL);
            return 0;

		case VK_UP:
		case VK_DOWN:
			PostMessage(hWnd, WM_COMMAND, CMD_CHANGE_SERVICE, NULL);
			return 0;

        case VK_TTALK:
        case VK_TACTION:
            PostMessage(hWnd, WM_COMMAND, CMD_GREEN_BUTTON, NULL);
            return 0;

        case VK_BACK:  // "backspace"
        case VK_TBACK: // also "backspace"
        case VK_RWIN:  // also "backspace"
            if (bKeyDown) {
                // handle key repeat for "back" only
                if (tcButtonPressed == VK_BACK)
                    PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT, (LPARAM)tcButtonPressed);
                return 0;
            }
            // yes, this is meant to fall through

		case VK_TSOFT1:
		case VK_TSOFT2:
			MessageBox(hWnd, TEXT("soft buttons"), TEXT("iDialer"), 0);
        case VK_T0:
        case VK_T1:
        case VK_T2:
        case VK_T3:
        case VK_T4:
        case VK_T5:
        case VK_T6:
        case VK_T7:
        case VK_T8:
        case VK_T9:
        case VK_TSTAR:
        case VK_TPOUND:
            bKeyDown = true;
            tcButtonPressed = GetDigitFor(wParam);
            CalculateClickRegion(tcButtonPressed);
            SetTimer(hWnd, IDT_TIMER_LONG_PRESS, 100, NULL);
            InvalidateRect(hWnd, &rClickRegion, false);
            PlayDTMF(tcButtonPressed, uVolume);
            return 0;
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoKeyUp - Process WM_KEYUP message for window
//
LRESULT DoKeyUp (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {

    bKeyDown = false;
    InvalidateRect(hWnd, &rClickRegion, false);
    KillTimer(hWnd, IDT_TIMER_LONG_PRESS);
    StopDTMF();

    switch (wParam) {
        case VK_LEFT:
        case VK_RIGHT:
        case VK_TTALK:
        case VK_TACTION:
            return 0;

		// It appears that VK_TSOFT1 and VK_TSOFT2 don't trigger an onkeydown event...
		case VK_TSOFT1:
            PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT, VK_TSOFT1);
            tcButtonPressed = 0;
			return 0;

		case VK_TSOFT2:
            PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT, VK_BACK);
            tcButtonPressed = 0;
			return 0;

        default:
            if (0 != tcButtonPressed) {
                PostMessage(hWnd, WM_COMMAND, CMD_DIAL_DIGIT,
                    (LPARAM)tcButtonPressed);
                tcButtonPressed = 0;
            }
            return 0;
    }

    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}

//-----------------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) {
   
    // Destroy child window if it exists
    if (hwndHTML) {
        DestroyWindow(hwndHTML);
        hwndHTML = 0;
    }

    // Uninitialize the COM classes
    CoUninitialize();

    // Remove notifications
    RegistryCloseNotification(hNotify);

    // Clean up titlebar
	if (bShowFullScreen)
	    DestroyTitlebar();

    // Quit
    PostQuitMessage (0);
    return 0;
}

//=============================================================================
// Utility functions
//

void InitSurface(HWND hWnd) {
    HRESULT hr = S_OK;
	HDC hdc;
	hdc = GetDC(hWnd);

    // Update the RECTs for the individual sections
	GetClientRect(hWnd, &rScreen);

    // Title bar, with date, carrier, battery, signal strength, etc.
	CopyRect(&rTitlebar, &rScreen);

	// Do not display titlebar if we're not full screen (even if we want to be)
	if (!bShowFullScreen 
		|| GetSystemMetrics(SM_CYSCREEN) > rScreen.bottom - rScreen.top) {

		// collapse custom titlebar so it is not active
		rTitlebar.bottom = rTitlebar.top;
	}
	else {
		rTitlebar.bottom = rTitlebar.top + TITLE_BAR_HEIGHT * vga;
	}

    // Menu at the bottom of the screen
    // Don't bother with the bottom menus if iContact doesn't exist
	CopyRect(&rMenubar, &rScreen);
	rMenubar.top = rMenubar.bottom 
		- (biContactExists ? SKIN_MENU_BAR_HEIGHT * vga : 0);

    // Between the titlebar and the menubar
	CopyRect(&rContent, &rScreen);
    rContent.top = rTitlebar.bottom;
    rContent.bottom = rMenubar.top;

    // The region where the digits dialed show up
	CopyRect(&rDigits, &rContent);
    rDigits.bottom = rDigits.top 
        + max(rScreen.right - rScreen.left, 
        rScreen.bottom - rScreen.top) / 8;

    // All the buttons
    // portrait (or square)
    if (rScreen.bottom >= rScreen.right) {
        rect1.top = rect2.top = rect3.top = rDigits.bottom;
        rect1.bottom = rect2.bottom = rect3.bottom 
            = rect4.top = rect5.top = rect6.top 
            = (rContent.bottom - rDigits.bottom) / 5 + rect1.top;
        rect4.bottom = rect5.bottom = rect6.bottom
            = rect7.top = rect8.top = rect9.top
            = (rContent.bottom - rDigits.bottom) / 5 + rect4.top;
        rect7.bottom = rect8.bottom = rect9.bottom
            = rects.top = rect0.top = rectp.top
            = (rContent.bottom - rDigits.bottom) / 5 + rect7.top;
        rects.bottom = rect0.bottom = rectp.bottom
            = rAddButton.top = rCallButton.top = rBackButton.top
            = (rContent.bottom - rDigits.bottom) / 5 + rects.top;
        rAddButton.bottom = rCallButton.bottom = rBackButton.bottom
            = rContent.bottom;

        rect1.left = rect4.left = rect7.left = rects.left = rAddButton.left
            = rContent.left;
        rect1.right = rect4.right = rect7.right = rects.right = rAddButton.right
            = rect2.left = rect5.left = rect8.left = rect0.left = rCallButton.left
            = (rContent.right - rContent.left) / 3 + rContent.left;
        rect2.right = rect5.right = rect8.right = rect0.right = rCallButton.right
            = rect3.left = rect6.left = rect9.left = rectp.left = rBackButton.left
            = rContent.right - (rContent.right - rContent.left) / 3;
        rect3.right = rect6.right = rect9.right = rectp.right = rBackButton.right
            = rContent.right;
    }
    //landscape
    else {
        rect1.top = rect2.top = rect3.top = rBackButton.top = rDigits.bottom;
        rect1.bottom = rect2.bottom = rect3.bottom 
            = rect4.top = rect5.top = rect6.top 
            = (rContent.bottom - rDigits.bottom) / 4 + rect1.top;
        rect4.bottom = rect5.bottom = rect6.bottom
            = rect7.top = rect8.top = rect9.top
            = (rContent.bottom - rDigits.bottom) / 4 + rect4.top;
        rect7.bottom = rect8.bottom = rect9.bottom
            = rects.top = rect0.top = rectp.top
            = (rContent.bottom - rDigits.bottom) / 4 + rect7.top;
        rects.bottom = rect0.bottom = rectp.bottom = rAddButton.bottom
            = rContent.bottom;
        rBackButton.bottom = rCallButton.top 
            = rBackButton.top + (rContent.bottom - rDigits.bottom) / 3;
        rAddButton.top = rCallButton.bottom
            = rContent.bottom - (rContent.bottom - rDigits.bottom) / 3;


        rect1.left = rect4.left = rect7.left = rects.left
            = rContent.left;
        rect1.right = rect4.right = rect7.right = rects.right
            = rect2.left = rect5.left = rect8.left = rect0.left
            = (rContent.right - rContent.left) / 4 + rContent.left;
        rect2.right = rect5.right = rect8.right = rect0.right
            = rect3.left = rect6.left = rect9.left = rectp.left
            = (rContent.right - rContent.left) / 4 + rect2.left;
        rect3.right = rect6.right = rect9.right = rectp.right
            = rBackButton.left = rCallButton.left = rAddButton.left
            = (rContent.right - rContent.left) / 4 + rect3.left;
        rBackButton.right = rCallButton.right = rAddButton.right
            = rContent.right;
    }

    // Initialize the DCs and Bitmaps
    if (hdcMem)
        CBR(DeleteDC(hdcMem));
    hdcMem = CreateCompatibleDC(hdc);
    if (hbmMem)
        CBR(DeleteObject(hbmMem));
	hbmMem = CreateCompatibleBitmap(hdc, rScreen.right - rScreen.left, 
        rScreen.bottom - rScreen.top);
    SelectObject(hdcMem, hbmMem);

    if (hdcContent)
        CBR(DeleteDC(hdcContent));
    hdcContent = CreateCompatibleDC(hdc);
    if (hbmContent)
        CBR(DeleteObject(hbmContent));
    hbmContent = CreateCompatibleBitmap(hdc, rScreen.right - rScreen.left, 
        rScreen.bottom - rScreen.top);
    SelectObject(hdcContent, hbmContent);

    if (hdcTmp)
        CBR(DeleteDC(hdcTmp));
    hdcTmp = CreateCompatibleDC(hdc);
    if (hbmTmp)
        CBR(DeleteObject(hbmTmp));
    hbmTmp = CreateCompatibleBitmap(hdc, rScreen.right - rScreen.left, 
        rScreen.bottom - rScreen.top);
    SelectObject(hdcTmp, hbmTmp);

    // Initialize skin
    if (!hbmSkin) {
        HBITMAP hbmSkinFile = SHLoadImageFile(skinFilename);
		BITMAP bmp;
		GetObject(hbmSkinFile, sizeof(bmp), &bmp);

	    // Load skin
		HGDIOBJ hTmpOld = SelectObject(hdcTmp, hbmSkinFile);

		// Create skin bitmap
	    hdcSkin = CreateCompatibleDC(hdc);
		hbmSkin = CreateCompatibleBitmap(hdc, DEFAULT_SCREEN_WIDTH * vga, 
			DEFAULT_SKIN_HEIGHT * vga);
        SelectObject(hdcSkin, hbmSkin);

		// Stretch skin to properly fit the screen
		StretchBlt(hdcSkin, 0, 0, DEFAULT_SCREEN_WIDTH * vga, 
			DEFAULT_SKIN_HEIGHT * vga,
			hdcTmp, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);

		// Restore the original hdcTmp bitmap
		SelectObject(hdcTmp, hTmpOld);
    }

    // Cache main content in off-screen bitmap, for speed
    DrawContentOn(hdcContent);

    CBR(ReleaseDC(hWnd, hdc));

Error:
    ASSERT(SUCCEEDED(hr));
}

COLORREF GetSkinRGB(int index) {
	COLORREF c = GetPixel(hdcSkin, index * vga, SKIN_COLORS_Y_OFFSET * vga);
	return c;
}

TCHAR GetDigitFor(POINT pt) {

    if (PtInRect(&rect1, pt))
        return '1';
    else if (PtInRect(&rect2, pt))
        return '2';
    else if (PtInRect(&rect3, pt))
        return '3';
    else if (PtInRect(&rect4, pt))
        return '4';
    else if (PtInRect(&rect5, pt))
        return '5';
    else if (PtInRect(&rect6, pt))
        return '6';
    else if (PtInRect(&rect7, pt))
        return '7';
    else if (PtInRect(&rect8, pt))
        return '8';
    else if (PtInRect(&rect9, pt))
        return '9';
    else if (PtInRect(&rect0, pt))
        return '0';
    else if (PtInRect(&rects, pt))
        return '*';
    else if (PtInRect(&rectp, pt))
        return '#';
    else if (PtInRect(&rAddButton, pt))
        return VK_TSOFT1;
    else if (PtInRect(&rCallButton, pt))
        return VK_RETURN;
    else if (PtInRect(&rBackButton, pt))
        return VK_BACK;

    return 0;
}

TCHAR GetDigitFor (int key) {
    switch (key) {
        case VK_RETURN:
        case VK_BACK:
        case VK_TSOFT1:
            return key;
        case VK_TBACK:
        case VK_TSOFT2:
            return VK_BACK;
        case VK_T0:
            return '0';
        case VK_T1:
            return '1';
        case VK_T2:
            return '2';
        case VK_T3:
            return '3';
        case VK_T4:
            return '4';
        case VK_T5:
            return '5';
        case VK_T6:
            return '6';
        case VK_T7:
            return '7';
        case VK_T8:
            return '8';
        case VK_T9:
            return '9';
        case VK_TSTAR:
            return '*';
        case VK_TPOUND:
            return '#';
    }
    return 0;
}

void CalculateClickRegion(POINT pt) {
    // They clicked elsewhere in the titlebar
    if (PtInRect(&rTitlebar, pt)) {
        rClickRegion = rTitlebar;
    }

    // They clicked in the bottom menus
    else if (PtInRect(&rMenubar, pt)) {
        rClickRegion = rMenubar;
    }

    // They clicked on one of the main buttons
    else if (PtInRect(&rect1, pt))
        rClickRegion = rect1;
    else if (PtInRect(&rect2, pt))
        rClickRegion = rect2;
    else if (PtInRect(&rect3, pt))
        rClickRegion = rect3;
    else if (PtInRect(&rect4, pt))
        rClickRegion = rect4;
    else if (PtInRect(&rect5, pt))
        rClickRegion = rect5;
    else if (PtInRect(&rect6, pt))
        rClickRegion = rect6;
    else if (PtInRect(&rect7, pt))
        rClickRegion = rect7;
    else if (PtInRect(&rect8, pt))
        rClickRegion = rect8;
    else if (PtInRect(&rect9, pt))
        rClickRegion = rect9;
    else if (PtInRect(&rect0, pt))
        rClickRegion = rect0;
    else if (PtInRect(&rects, pt))
        rClickRegion = rects;
    else if (PtInRect(&rectp, pt))
        rClickRegion = rectp;
    else if (PtInRect(&rAddButton, pt))
        rClickRegion = rAddButton;
    else if (PtInRect(&rCallButton, pt))
        rClickRegion = rCallButton;
    else if (PtInRect(&rBackButton, pt))
        rClickRegion = rBackButton;

    // Clicked on a non-clickable region
    else {
        rClickRegion.top = -1;
        rClickRegion.bottom = -1;
    }
}

void CalculateClickRegion(TCHAR key) {
        switch (key) {
        case VK_RETURN:
            rClickRegion = rCallButton;
            break;
        case VK_TSOFT1:
            rClickRegion = rAddButton;
            break;
        case VK_BACK:
            rClickRegion = rBackButton;
            break;
        case VK_T0:
            rClickRegion = rect0;
            break;
        case VK_T1:
            rClickRegion = rect1;
            break;
        case VK_T2:
            rClickRegion = rect2;
            break;
        case VK_T3:
            rClickRegion = rect3;
            break;
        case VK_T4:
            rClickRegion = rect4;
            break;
        case VK_T5:
            rClickRegion = rect5;
            break;
        case VK_T6:
            rClickRegion = rect6;
            break;
        case VK_T7:
            rClickRegion = rect7;
            break;
        case VK_T8:
            rClickRegion = rect8;
            break;
        case VK_T9:
            rClickRegion = rect9;
            break;
        case VK_TSTAR:
            rClickRegion = rects;
            break;
        case VK_TPOUND:
            rClickRegion = rectp;
            break;
        default:
            rClickRegion.top = -1;
            rClickRegion.bottom = -1;
            break;
    }
}

void StripNonDigits(TCHAR * to, const TCHAR * from, int cchTo) {
    int cTo = 0;
    
    for (int i = 0; i < (int)_tcslen(from) && i < cchTo - 1; i++) {
        if (_tcschr(VALID_DIGITS, from[i]))
            to[cTo++] = from[i];
    }
    to[cTo] = 0;
}

void ScrubPhoneNumber(TCHAR * szNumber, int cchNumber) {
    TCHAR buffer[MAX_DIALSTRING] = {0};

    TCHAR * pos = szNumber;
    if (*pos == '+') {
        StringCchCopy(buffer, MAX_DIALSTRING, szNumber);
    }
    else if (_tcsstr(szNumber, szCountryCode) == pos) {
        StringCchPrintf(buffer, MAX_DIALSTRING, TEXT("+%s"),
            szNumber);
    }
    else {
        StringCchPrintf(buffer, MAX_DIALSTRING, TEXT("+%s%s"),
            szCountryCode, szNumber);
    }

    StringCchCopy(szNumber, cchNumber, buffer);
}

void FillDialString() {
    int lNumber = _tcslen(dialNumber);
    int lDialPlan = _tcslen(dialPlan);

    int cNumber = 0;
    int cDialString = 0;
    bool match = true;

    // Try to match up the DialNumber 
    // with one of the lines in DialPlan.
    // The DialPlan will be stored as a [\n\r;]+ seperated list

    for (int cDialPlan = 0; cDialPlan < lDialPlan; cDialPlan++) {

        switch (dialPlan[cDialPlan]) {
            case '\r':
            case '\n':
            case ';':
                if (match && cNumber == lNumber) {
                    // SUCCESS!
                    goto MATCHED;
                }

                cDialString = 0;
                cNumber = 0;
                match = true;
                break;

            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '0':
            case '+':
            case '#':
            case '*':
                if (cNumber < lNumber 
                    && dialPlan[cDialPlan] == dialNumber[cNumber])
                    dialString[cDialString] = dialNumber[cNumber];
                else
                    match = false;

                cDialString++;
                cNumber++;
                break;

            case 'N':
                if (cNumber < lNumber 
                    && dialNumber[cNumber] >= '0' 
                    && dialNumber[cNumber] <= '9') {
                    dialString[cDialString] = dialNumber[cNumber];
                    cNumber++;
                }
                else if (cNumber == lNumber) {
                    dialString[cDialString] = ' ';
                }
                else {
                    match = false;
                }
                cDialString++;
                break;

            case '_':
                dialString[cDialString++] = ' ';
                break;

            default:
                dialString[cDialString] = dialPlan[cDialPlan];
                cDialString++;
                break;
        }
    }

    if (cNumber != lNumber) {
        // no matches, fall back to just copying the number
        StringCchCopy(dialString, MAX_DIALSTRING, dialNumber);
        return;
    }

MATCHED:
    dialString[cDialString] = 0;

    // post-process the string:

    // parenthesis-space means there are no more numbers...
    TCHAR * paren = wcsstr(dialString, TEXT("( "));
    if (paren)
        *paren = 0;

    // space or "-" should be removed from the end
    while (_tcslen(dialString) > 0 
        && (
               dialString[_tcslen(dialString) - 1] == ' '
            || dialString[_tcslen(dialString) - 1] == '-'
           )
    ) {
        dialString[_tcslen(dialString) - 1] = 0;
    }

    //done!
}

void AddContact() {
    HRESULT hr = S_OK;
    IContact * pContact = NULL;
    IItem * pItem = NULL;
    IPOutlookApp2 * polApp;
	BSTR bstrNumber;
    TCHAR * pNumber = &dialString[0];
    HWND hWnd = 0;
    
    // Initialize COM libraries (for POOM)
    hr = CoInitializeEx(NULL, 0);
    CHR(hr);

    hr = CoCreateInstance(__uuidof(Application), NULL, CLSCTX_INPROC_SERVER,
                          __uuidof(IPOutlookApp2), (LPVOID*) &polApp);
    CHR(hr);
    hWnd = FindWindow (SZ_APP_NAME, NULL);
    hr = polApp->Logon((long)hWnd);
    CHR(hr);

    hr = polApp->CreateItem(olContactItem, (IDispatch**)&pContact);
    CHR(hr);

	bstrNumber = ::SysAllocString(pNumber);
    pContact->put_MobileTelephoneNumber(bstrNumber);

    hr = ((IDispatch*)pContact)->QueryInterface(__uuidof(IItem), 
        (LPVOID*)&pItem);

    CHR(hr);

	hr = pItem->Edit(hWnd);

Error:
    // cleanup
    RELEASE_OBJ(pContact);
    RELEASE_OBJ(polApp);
	::SysFreeString(bstrNumber);

    return;
}

HRESULT Dial(const TCHAR * szNumber, const TCHAR * szName, int which) {
    HRESULT hr = E_FAIL;
    
    // TODO: better number validation
    CBR(_tcslen(szNumber) >= 1);

	if (!_tcscmp(szNumber, TEXT("911"))) {
        hr = DialStandard(szNumber, szName);
        CHR(hr);
        PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
	}

    switch (services[which].type) {
        case Phone:
            hr = DialStandard(szNumber, szName);
            CHR(hr);
            PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
            break;

		case CallingCard:
			hr = DialCallingCard(szNumber, szName, &services[which]);
			CHR(hr);
            PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
			break;

		case GoogleVoice:
			hr = DialGoogleVoice(szNumber, szName, &services[which]);
			CHR(hr);
			break;

        case JaJah:
            hr = DialJaJah(szNumber, szName, &services[which]);
            CHR(hr);
            PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
            break;

		case Command:
            hr = DialCommand(szNumber, szName, &services[which]);
            CHR(hr);
            PostMessage(hwndMain, WM_COMMAND, CMD_PLACE_CALL_COMPLETE, NULL);
            break;

        default:
            CHR(E_FAIL);
            break;
    }

    hr = S_OK;

Error:
    return hr;
}

// Splits a string into multiple strings
// This will modify szData, by converting "sep" into "\0"
// Sets s1, s2, s3 to the parts of the string
void SplitString(TCHAR * pszOutput, size_t cchOutput, const TCHAR * pszInput, 
                 TCHAR sep, TCHAR ** s1, TCHAR ** s2, TCHAR ** s3) {

    StringCchCopy(pszOutput, cchOutput, pszInput);
    *s1 = pszOutput;

    if (!s2)
        return;

    *s2 = _tcschr(*s1, sep);
    if (*s2 == 0) {
        *s2 = *s1 + _tcslen(*s1);
    }
    else {
        **s2 = '\0';
        (*s2)++;
    }

    if (!s3)
        return;

    *s3 = _tcschr(*s2, sep);
    if (*s3 == 0) {
        *s3 = *s2 + _tcslen(*s2);
    }
    else {
        **s3 = '\0';
        (*s3)++;
    }
    
    return;
}

// Replaces szSearch in pszInput with pszSubstitute
void ExpandString(TCHAR * pszOutput, size_t cchOutput, 
                 const TCHAR * pszSearch, const TCHAR * pszSubstitute) {

	 TCHAR buffer[MAX_PATH];
	 TCHAR * src = &buffer[0];
	 TCHAR * search;
	 int len = _tcslen(pszSearch);

	 // initialize pszOutput
	 StringCchCopy(buffer, MAX_PATH, pszOutput);
	 *pszOutput = 0;

	 // substitute all occurrences of pszSearch into pszOutput
	 while (search = _tcsstr(src, pszSearch)) {
		 StringCchCatN(pszOutput, cchOutput, src, search - src);
		 StringCchCat(pszOutput, cchOutput, pszSubstitute);
		 src = search + len;
	 }

	 // copy the remaining bytes
	 StringCchCat(pszOutput, cchOutput, src);
}

HRESULT DialStandard(const TCHAR * szNumber, const TCHAR * szName) {
    
#if DEBUG
    MessageBox(hwndMain, szNumber, TEXT("dialing..."), 0);
#endif

    LRESULT lResult = tapiRequestMakeCall(szNumber, NULL, szName, NULL);
    return lResult == 0 ? S_OK : E_FAIL;
}

HRESULT DialCallingCard(const TCHAR * szNumber, const TCHAR * szName,
						SERVICE * pService) {

    TCHAR szFullNumber[MAX_DIALSTRING] = {0};

    TCHAR szData[128] = {0};
    TCHAR * pszPrefix;
    TCHAR * pszSuffix;
    SplitString(szData, 128, pService->data, ';', &pszPrefix, &pszSuffix);

    // do not prepend if the number already begins with the same prefix
    if (pszPrefix > 0 && _tcsstr(szNumber, pszPrefix) != szNumber)
        StringCchCat(szFullNumber, MAX_DIALSTRING, pszPrefix);

    // replace "+" at beginning of the string with "011"
    if (szNumber[0] == '+') {
        // (only prepend international prefix if dialing internationally)
        if (_tcsstr(szNumber, szCountryCode) != &szNumber[1]) {
            StringCchCat(szFullNumber, MAX_DIALSTRING, szIntlPrefix);
        }
        StringCchCat(szFullNumber, MAX_DIALSTRING, &szNumber[1]);
    }
    else {
        StringCchCat(szFullNumber, MAX_DIALSTRING, szNumber);
    }

    // do not append if the number already ends with the same suffix
    int offset = max(0, _tcslen(szNumber) - _tcslen(pszSuffix));
    if (_tcsstr(szNumber, pszSuffix) != szNumber + offset)
        StringCchCat(szFullNumber, MAX_DIALSTRING, pszSuffix);

	return DialStandard(szFullNumber, szName);
}

HRESULT DialGoogleVoice(const TCHAR * szNumber, const TCHAR * szName,
						SERVICE * pService) {
    
	WNDCLASS    wc = { 0 };
    HRESULT     hr = S_OK;
	HWND hwndParent = GetForegroundWindow();

    SetCursor(LoadCursor(NULL, IDC_WAIT));

    // create web browser child window
	if (hwndHTML == 0) {
    	VERIFY(InitHTMLControl(hInst));

		int padding = 10 * vga;
        int taskbar = 25 * vga;
		hwndHTML = CreateWindow(
			WC_HTML, NULL,
            WS_CHILD | WS_BORDER | WS_VISIBLE,
            padding, padding,
            rScreen.right - 2 * padding,
            rScreen.bottom - 2 * padding - taskbar,
            hwndParent, NULL, hInst, NULL);
	}
    CBR(hwndHTML != NULL);

    // Navigate that web browser child to google voice.
    // WM_NOTIFY handling will take care of the rest.
    // why /m/sms ? Because it's a short page that 
    // also contains the _rnr_se
    BSTR bstrURL = SysAllocString(
        TEXT("https://www.google.com/voice/m/sms?"));
    hr = SendMessage(hwndHTML, DTM_NAVIGATE, 0, (LPARAM)bstrURL);
    SysFreeString(bstrURL);
	CHR(hr);

	hr = S_OK;
Error:
    if (FAILED(hr))
        SetCursor(NULL);
    return hr;
}

HRESULT DialJaJah(const TCHAR * szNumber, const TCHAR * szName,
				  SERVICE * pService) {

    HRESULT hr = E_FAIL;

    SetCursor(LoadCursor(NULL, IDC_WAIT));

    TCHAR szData[128] = {0};
    TCHAR * pszUsername;
    TCHAR * pszPassword;
    TCHAR * pszCallback;
    SplitString(szData, 128, pService->data, ';',
        &pszUsername, &pszPassword, &pszCallback);

    if (_tcslen(pszCallback) == 0)
        pszCallback = szMyNumber;

    // Get scrubbed "to" number
    TCHAR szToNumber[MAX_DIALSTRING] = {0};
    StringCchCopy(szToNumber, MAX_DIALSTRING, szNumber);
    ScrubPhoneNumber(szToNumber, MAX_DIALSTRING);

    // &pszCallback[1] because JaJah doesn't want the "+" in the request
    hr = HttpJaJahDial(&pszCallback[1], &szToNumber[1], 
        pszUsername, pszPassword);

    if (FAILED(hr)) {
        TCHAR szErrorMessage[MAX_PATH];
        GetHttpErrorMessage(szErrorMessage, MAX_PATH);
        MessageBox(NULL, szErrorMessage, SZ_APP_NAME, 0);

        StringCchPrintf(szErrorMessage, MAX_PATH, TEXT("from: %s\nto: %s"),
            &pszCallback[1], szToNumber);
        MessageBox(NULL, szErrorMessage, SZ_APP_NAME, 0);

        goto Error;
    }

    hr = S_OK;

Error:
    SetCursor(NULL);
    return hr;
}

HRESULT DialCommand(const TCHAR * szNumber, const TCHAR * szName, SERVICE * pService) {
	TCHAR szCommand[MAX_PATH] = {0};
	TCHAR szArgs[MAX_PATH] = {0};
    PROCESS_INFORMATION pi;
	HRESULT hr = S_OK;

	// Data will be of the format:
	// Path\Program.exe;-arg1=%number% -arg2=%name%
	// where:
	// Path, if relative, to where iDialer is installed, up 2 (i.e. \Program Files)
	//       if absolute, can be any path
	// %number% will be replaced with number to be dialed
	// %name% will be replaced with name to be dialed
	TCHAR szData[MAX_PATH] = {0};
	StringCchCopy(szData, MAX_PATH, pService->data);
	ExpandString(szData, MAX_PATH, TEXT("%number%"), szNumber);
	ExpandString(szData, MAX_PATH, TEXT("%name%"), szName);

	// the part after the semicolon is the arguments
	TCHAR * args = _tcschr(szData, ';');
	CBR(args);
	*(args++) = '\0';

	// the part before the semicolon is the path info
    if (szData[0] == '\\') {
		// absolute directory specified
        StringCchCopy(szCommand, MAX_PATH, szData);
    }
    else {
        // relative filename specified, look up2 directories
		::GetModuleFileName(NULL, szCommand, MAX_PATH);
		TCHAR * pstr = _tcsrchr(szCommand, '\\');
		if (pstr) *pstr = '\0';
		pstr = _tcsrchr(szCommand, '\\');
		if (pstr) *(++pstr) = '\0';
		StringCchCat(szCommand, MAX_PATH, szData);
    }

#if DEBUG
	TCHAR szDebugBuffer[MAX_PATH];
	StringCchPrintf(szDebugBuffer, MAX_PATH, TEXT("%s %s"), szCommand, args);
    MessageBox(hwndMain, szDebugBuffer, TEXT("running..."), 0);
#endif

    CreateProcess(szCommand, args,
		NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &pi);

Error:
	return hr;
}

void DrawContentOn(HDC hdc) {

    // Draw the digits region background
    DrawNumberRegion(hdc, rDigits);

    // Draw the digit buttons
    DrawDigitButton(hdc, rect1, '1', TEXT(""));
    DrawDigitButton(hdc, rect2, '2', TEXT("ABC"));
    DrawDigitButton(hdc, rect3, '3', TEXT("DEF"));
    DrawDigitButton(hdc, rect4, '4', TEXT("GHI"));
    DrawDigitButton(hdc, rect5, '5', TEXT("JKL"));
    DrawDigitButton(hdc, rect6, '6', TEXT("MNO"));
    DrawDigitButton(hdc, rect7, '7', TEXT("PQRS"));
    DrawDigitButton(hdc, rect8, '8', TEXT("TUV"));
    DrawDigitButton(hdc, rect9, '9', TEXT("WXYZ"));
    DrawDigitButton(hdc, rects, '*', TEXT(""));
    DrawDigitButton(hdc, rect0, '0', TEXT("+"));
    DrawDigitButton(hdc, rectp, '#', TEXT(""));

    // Draw the function buttons

    DrawAddButton(hdc, rAddButton);
    DrawCallButton(hdc, rCallButton);
    DrawBackButton(hdc, rBackButton);
}

void DrawGlassySurface(HDC hdc, RECT rect, UINT index1, UINT index2,
					   UINT index3, UINT index4) { 

	unsigned int Shift = 8;
	COLORREF rgb0 = GetSkinRGB(index1);
	COLORREF rgb1 = GetSkinRGB(index2);
	COLORREF rgb2 = GetSkinRGB(index3);
	COLORREF rgb3 = GetSkinRGB(index4);

	TRIVERTEX        vert[6];
	GRADIENT_RECT    gRect[3];
	
    vert [0] .x      = rect.left;
	vert [0] .y      = rect.top;
	vert [0] .Red    = GetRValue(rgb0) << Shift;
	vert [0] .Green  = GetGValue(rgb0) << Shift;
	vert [0] .Blue   = GetBValue(rgb0) << Shift;
	vert [0] .Alpha  = 0x0000;

    vert [1] .x      = rect.right;
	vert [1] .y      = (rect.bottom - rect.top) / 2 - 2 + rect.top; 
	vert [1] .Red    = GetRValue(rgb1) << Shift;
	vert [1] .Green  = GetGValue(rgb1) << Shift;
	vert [1] .Blue   = GetBValue(rgb1) << Shift;
	vert [1] .Alpha  = 0x0000;

    vert [2] .x      = rect.left;
    vert [2] .y      = vert[1].y;
    vert [2] .Red    = vert[1].Red;
    vert [2] .Green  = vert[1].Green;
    vert [2] .Blue   = vert[1].Blue;
    vert [2] .Alpha  = vert[1].Alpha;

    vert [3] .x      = rect.right;
	vert [3] .y      = (rect.bottom - rect.top) / 2 + 2 + rect.top; 
	vert [3] .Red    = GetRValue(rgb2) << Shift;
	vert [3] .Green  = GetGValue(rgb2) << Shift;
	vert [3] .Blue   = GetBValue(rgb2) << Shift;
	vert [3] .Alpha  = 0x0000;

    vert [4] .x      = rect.left;
    vert [4] .y      = vert[3].y;
    vert [4] .Red    = vert[3].Red;
    vert [4] .Green  = vert[3].Green;
    vert [4] .Blue   = vert[3].Blue;
    vert [4] .Alpha  = vert[3].Alpha;

	vert [5] .x      = rect.right;
	vert [5] .y      = rect.bottom; 
	vert [5] .Red    = GetRValue(rgb3) << Shift;
	vert [5] .Green  = GetGValue(rgb3) << Shift;
	vert [5] .Blue   = GetBValue(rgb3) << Shift;
	vert [5] .Alpha  = 0x0000;
	
    gRect [0] .UpperLeft  = 0;
	gRect [0] .LowerRight = 1;
    gRect [1] .UpperLeft  = 2;
    gRect [1] .LowerRight = 3;
    gRect [2] .UpperLeft  = 4;
    gRect [2] .LowerRight = 5;

	GradientFill(hdc, vert, 6, gRect, 3, GRADIENT_FILL_RECT_V);
}

void DrawDigitButton(HDC hdc, RECT rect, TCHAR digit, TCHAR * label) {
	unsigned int Shift = 8;

	TRIVERTEX       vert[2];
	GRADIENT_RECT   gRect;
    COLORREF		rgb0 = GetSkinRGB(SKIN_COLOR_DIGIT_BUTTON_RGB1);
    COLORREF		rgb1 = GetSkinRGB(SKIN_COLOR_DIGIT_BUTTON_RGB2);
    HFONT			hfOld;
	
    vert [0] .x      = rect.left;
	vert [0] .y      = rect.top;
	vert [0] .Red    = GetRValue(rgb0) << Shift;
	vert [0] .Green  = GetGValue(rgb0) << Shift;
	vert [0] .Blue   = GetBValue(rgb0) << Shift;
	vert [0] .Alpha  = 0x0000;

    vert [1] .x      = rect.right;
	vert [1] .y      = rect.bottom; 
	vert [1] .Red    = GetRValue(rgb1) << Shift;
	vert [1] .Green  = GetGValue(rgb1) << Shift;
	vert [1] .Blue   = GetBValue(rgb1) << Shift;
	vert [1] .Alpha  = 0x0000;
    
    gRect.UpperLeft  = 0;
	gRect.LowerRight = 1;

    GradientFill(hdc, vert, 2, &gRect, 1, GRADIENT_FILL_RECT_V);

    // draw the shadow
    HPEN hpen = CreatePen(PS_SOLID, 1, GetSkinRGB(SKIN_COLOR_BUTTON_SHADOW));
    HGDIOBJ hold = SelectObject(hdc, hpen);
    MoveToEx(hdc, rect.left, rect.bottom - 1, NULL);
    LineTo(hdc, rect.right - 1, rect.bottom - 1);
    LineTo(hdc, rect.right - 1, rect.top);
    SelectObject(hdc, hold);
    DeleteObject(hpen);

    // draw the highlight
    hpen = CreatePen(PS_SOLID, 1, GetSkinRGB(SKIN_COLOR_BUTTON_HIGHLIGHT));
    hold = SelectObject(hdc, hpen);
    LineTo(hdc, rect.left, rect.top);
    LineTo(hdc, rect.left, rect.bottom - 1);
    SelectObject(hdc, hold);
    DeleteObject(hpen);

    // draw the text
    hfOld = (HFONT)SelectObject(hdc, hfDigitFont);
    SetTextAlign(hdc, TA_CENTER);
	SetTextColor(hdc, GetSkinRGB(SKIN_COLOR_TEXT));
	SetBkMode(hdc, TRANSPARENT);
    int x = (rect.right - rect.left) / 2 + rect.left;
    int y = (rect.bottom - rect.top - DIGIT_FONT_SIZE * vga - CAPTION_FONT_SIZE * vga) / 2
        + rect.top;
    ExtTextOut(hdc, x, y, NULL, NULL, &digit, 1, 0);

    // draw the caption "ABC", etc.
    int len = _tcslen(label);
    if (len > 0) {
        SelectObject(hdc, hfCaptionFont);
	    SetTextColor(hdc, GetSkinRGB(SKIN_COLOR_TEXT_DIM));
	    SetBkMode(hdc, TRANSPARENT);
        ExtTextOut(hdc, x, y + DIGIT_FONT_SIZE * vga / 2 + CAPTION_FONT_SIZE * vga * 2/3,
            NULL, NULL, label, len, 0);
    }

    // restore original font
    SelectObject(hdcMem, hfOld);
}

void DrawNumberRegion(HDC hdc, RECT rect) {
    DrawGlassySurface(hdc, rect, SKIN_COLOR_NUMBER_REGION_RGB1,
		SKIN_COLOR_NUMBER_REGION_RGB2, SKIN_COLOR_NUMBER_REGION_RGB3,
		SKIN_COLOR_NUMBER_REGION_RGB4);
}

void DrawCallButton(HDC hdc, RECT rect) {
    HFONT hfOld;

    DrawGlassySurface(hdc, rect, SKIN_COLOR_CALL_BUTTON_RGB1,
		SKIN_COLOR_CALL_BUTTON_RGB2, SKIN_COLOR_CALL_BUTTON_RGB3,
		SKIN_COLOR_CALL_BUTTON_RGB4);

    // draw phone icon
    int yoffset = (rAddButton.bottom - rAddButton.top - 16 * vga) / 2 + vga;
    BitBlt(hdc, rCallButton.left + 8 * vga, rCallButton.top + yoffset,
        21 * vga, 16 * vga, hdcSkin, 86 * vga, 20 * vga, SRCCOPY);

    // draw the word "call"    
    hfOld = (HFONT)SelectObject(hdc, hfDigitFont);
    SetTextAlign(hdcMem, TA_CENTER);
	SetTextColor(hdcMem, GetSkinRGB(SKIN_COLOR_TEXT));
	SetBkMode(hdcMem, TRANSPARENT);
    int x = (rect.right - rect.left + 21 * vga) / 2 + rect.left;
    int y = (rect.bottom - rect.top - DIGIT_FONT_SIZE * vga) / 2 + rect.top;
    ExtTextOut(hdc, x, y, NULL, NULL, TEXT_CALL, 4, 0);

    // restore original font
    SelectObject(hdc, hfOld);
}

void DrawAddButton(HDC hdc, RECT rect) {
    DrawGlassySurface(hdc, rect, SKIN_COLOR_ADD_BUTTON_RGB1,
		SKIN_COLOR_ADD_BUTTON_RGB2, SKIN_COLOR_ADD_BUTTON_RGB3,
		SKIN_COLOR_ADD_BUTTON_RGB4);

    int yoffset = (rAddButton.bottom - rAddButton.top - 16 * vga) / 2 + vga;
    BitBlt(hdc, rAddButton.left + 25 * vga, rAddButton.top + yoffset,
        27 * vga, 16 * vga, hdcSkin, 25 * vga, 21 * vga, SRCCOPY);
}

void DrawBackButton(HDC hdc, RECT rect) {
    DrawGlassySurface(hdc, rect, SKIN_COLOR_BACK_BUTTON_RGB1,
		SKIN_COLOR_BACK_BUTTON_RGB2, SKIN_COLOR_BACK_BUTTON_RGB3,
		SKIN_COLOR_BACK_BUTTON_RGB4);

    // draw "back" icon
    int yoffset = (rAddButton.bottom - rAddButton.top - 16 * vga) / 2 + vga;
    BitBlt(hdc, rBackButton.left + 27 * vga, rBackButton.top + yoffset,
        29 * vga, 16 * vga, hdcSkin, 186 * vga, 21 * vga, SRCCOPY);
}

void LoadSettings() {
    TCHAR key[32] = {0};
    TCHAR value[128] = {0};

    // dial plan
    LoadSetting(dialPlan, MAX_DIALPLAN, SZ_IDIALER_REG_KEY, DIALPLAN,
        DIALPLAN_DEFAULT);

    // skin
	// can be "default" or "iphone" or "\Storare Card\my_skin.png"
    TCHAR szSkin[MAX_PATH];
    LoadSetting(szSkin, MAX_PATH, SZ_IDIALER_REG_KEY, SKIN, SKIN_DEFAULT);
	if (szSkin[0] == '\\') 
		StringCchCopy(skinFilename, MAX_PATH, szSkin);
	else
	    GetCurDirFilename(skinFilename, szSkin, TEXT("png"));
	
	// use the default skin anyway, if the specified skin can't be found
	if (!FileExists(skinFilename)) {
		GetCurDirFilename(skinFilename, SKIN_DEFAULT, TEXT("png"));

		// die if the skin _still_ can't be found
		if (!FileExists(skinFilename)) {
			HWND hWnd = FindWindow (SZ_APP_NAME, NULL);
			MessageBox(hWnd, TEXT("No skin found"), SZ_APP_NAME, 0);
			PostMessage(hWnd, WM_CLOSE, 0, 0);
		}
	}

    // country code
    LoadSetting(szCountryCode, MAX_COUNTRY_CODE, SZ_IDIALER_REG_KEY,
        COUNTRY_CODE, COUNTRY_CODE_DEFAULT);

    // international dialing prefix
    LoadSetting(szIntlPrefix, MAX_COUNTRY_CODE, SZ_IDIALER_REG_KEY,
        INTL_PREFIX, INTL_PREFIX_DEFAULT);

    // exit on minimize
    LoadSetting(value, 128, SZ_IDIALER_REG_KEY, EXIT_ON_MINIMIZE,
        EXIT_ON_MINIMIZE_DEFAULT);
    bExitOnMinimize = value[0] == '1';

#ifdef DEBUG
	bExitOnMinimize = false;
#endif

	// full screen (1 = custom titlebar, 0 = standard titlebar)
    LoadSetting(value, 128, SZ_IDIALER_REG_KEY, SHOW_FULL_SCREEN,
        SHOW_FULL_SCREEN_DEFAULT);
    bShowFullScreen = value[0] == '1';

	// volume
	LoadSetting(value, 128, SZ_IDIALER_REG_KEY, VOLUME_KEYPAD,
		VOLUME_KEYPAD_DEFAULT);
	uVolume = min(100, max(0, _ttol(value)));
	uVolume = MulDiv(uVolume, 0xFF, 100);
	uVolume |= uVolume << 8;

    // my number -- user may want to override own phone number for voicemail
    LoadSetting(szMyNumber, MAX_DIALSTRING, SZ_IDIALER_REG_KEY, MY_NUMBER);
    if (_tcslen(szMyNumber) == 0) {
        SHGetPhoneNumber(szMyNumber, MAX_DIALSTRING, 1);
        SaveSetting(SZ_IDIALER_REG_KEY, szMyNumber, MY_NUMBER);
    }
    ScrubPhoneNumber(szMyNumber, MAX_DIALSTRING);

    // services
    for (cServices = 1; cServices <= ARRAYSIZE(services); cServices++) {

        // service type
        StringCchPrintf(key, 32, SERVICE_TYPE_FORMAT, cServices);
        LoadSetting(value, 128, SZ_IDIALER_REG_KEY, key);
        if (0 == value[0])
            break;

		SERVICE * pService = &services[cServices-1];

        if (0 == _tcscmp(value, SERVICE_TYPE_PHONE)) {
            pService->type = Phone;
        }
        else if (0 == _tcscmp(value, SERVICE_TYPE_CALLINGCARD)) {
            pService->type = CallingCard;
        }
        else if (0 == _tcscmp(value, SERVICE_TYPE_GOOGLEVOICE)) {
            pService->type = GoogleVoice;
        }
        else if (0 == _tcscmp(value, SERVICE_TYPE_JAJAH)) {
            pService->type = JaJah;
        }
		else if (0 == _tcscmp(value, SERVICE_TYPE_COMMAND)) {
			pService->type = Command;
		}
        else {
            break;
        }

        // service title
        StringCchPrintf(key, 64, SERVICE_TITLE_FORMAT, cServices);
        LoadSetting(value, 64, SZ_IDIALER_REG_KEY, key);
        StringCchCopy(pService->title, 64, value);
		// Fill in with <name of carrier> if blank
		if (_tcslen(pService->title) == 0) {
			RegistryGetString(
				SN_PHONEOPERATORNAME_ROOT, 
				SN_PHONEOPERATORNAME_PATH, 
				SN_PHONEOPERATORNAME_VALUE, 
				pService->title, 64);
		}

        // service data
        StringCchPrintf(key, 128, SERVICE_DATA_FORMAT, cServices);
        LoadSetting(value, 128, SZ_IDIALER_REG_KEY, key);
        StringCchCopy(pService->data, 128, value);
    }

    // we need at least one service... make it just the phone
    cServices--;
    if (0 == cServices) {
        cServices = 1;
        services[0].type = Phone;
        services[0].title[0] = 0;
        services[0].data[0] = 0;
    }
    
    // which service is currently selected?
    LoadSetting(value, 128, SZ_IDIALER_REG_KEY, SERVICE_NUM, SERVICE_NUM_DEFAULT);
    nService = _wtoi(value) % cServices;

    if (0 == _tcslen(dialNumber)) {
        // fill the number with the most recently dialed number
        // but obvi don't dial it
        TCHAR buffer[MAX_DIALSTRING];
        LoadSetting(buffer, MAX_DIALSTRING, SZ_IDIALER_REG_KEY, LAST_DIALED_NUMBER);
        StripNonDigits(dialNumber, buffer, MAX_DIALSTRING);
    }
}

// A return value of "0" means to immediately exit iDialer
int ParseCommandLine(const TCHAR * tcszCmdLine) {
    TCHAR szBuffer[MAX_PATH];
    TCHAR szPhoneNumber[MAX_PATH] = {0};
    bDialImmediately = false;

    StringCchCopy(szBuffer, MAX_PATH, tcszCmdLine);

    // iDialer can be used as a command-line utility
    // with this argument: -choosecontact 
    // In this case, fill dialName and number
    if (_tcsstr(szBuffer, COMMAND_LINE_CHOOSECONTACT) != NULL) {
        if (FAILED(ChoosePhoneNumber(dialName, szBuffer)))
            return 0;
    }

    // iDialer can also be used as a command-line utility
    // with these arguments: mynumber [-name=myname]
    TCHAR * pname = _tcsstr(szBuffer, COMMAND_LINE_NAME);
    if (pname != NULL) {
        int nameLen = wcslen(pname) - wcslen(COMMAND_LINE_NAME);
        if (nameLen > 0) {
            StringCchCopyN(dialName, TAPIMAXCALLEDPARTYSIZE, 
                pname + wcslen(COMMAND_LINE_NAME), nameLen);
        }
        // erase the -name=... from the command line
        *pname = 0;
    }

    // first load into szPhoneNumber here so as to not override
    // dialNumber with an empty number
    StripNonDigits(szPhoneNumber, szBuffer, MAX_DIALSTRING);

    // This means there was no number to dial, so return, 
    // but don't dial immediately.
    if (_tcslen(szPhoneNumber) == 0)
        return 1;

    StringCchCopy(dialNumber, MAX_DIALSTRING, szPhoneNumber);
	FillDialString();

    // Special case: we can dial immediately without loading the 
    // main window as long as we are using the default dialer
    // or calling card.
	if (nService == -1) {}
	else if (services[nService].type == Phone) {
        SaveSetting(SZ_IDIALER_REG_KEY, dialNumber, LAST_DIALED_NUMBER);
        DialStandard(dialNumber, dialName);
        return 0;
    }
    else if (services[nService].type == CallingCard) {
        SaveSetting(SZ_IDIALER_REG_KEY, dialNumber, LAST_DIALED_NUMBER);
		DialCallingCard(dialNumber, dialName, &services[nService]);
        return 0;
    }

    bDialImmediately = true;
    return 1;
}
